package com.glsx.platform.protocol.common;

import com.sun.javadoc.*;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.xmlbeans.XmlException;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import org.w3c.dom.Element;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.math.BigInteger;

/**
 * Created by linxy on 2017/2/9.
 */


/*

对于无法应用docx默认的heading问题，解决方法是先在本地手动创建一个docx文件，
以这个文件为模板，将里边的heading拷贝出来，详细请见generateApiDoc

1. pom.xml配置

<dependencies>

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>3.16-beta1</version>
        </dependency>

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>3.16-beta1</version>
        </dependency>

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml-schemas</artifactId>
            <version>3.16-beta1</version>
        </dependency>

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-scratchpad</artifactId>
            <version>3.16-beta1</version>
        </dependency>

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>ooxml-schemas</artifactId>
            <version>1.3</version>
        </dependency>

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-contrib</artifactId>
            <version>3.7-beta3</version>
        </dependency>

    </dependencies>

<profiles>
        <profile>
            <id>default-tools.jar</id>
            <activation>
                <property>
                    <name>java.vendor</name>
                    <value>Oracle Corporation</value>
                </property>
            </activation>
            <dependencies>
                <dependency>
                    <groupId>com.sun</groupId>
                    <artifactId>tools</artifactId>
                    <version>1.4.2</version>
                    <scope>system</scope>
                    <systemPath>${java.home}/../lib/tools.jar</systemPath>
                </dependency>
            </dependencies>
        </profile>
    </profiles>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-javadoc-plugin</artifactId>
                <extensions>true</extensions>
                <executions>
                    <execution>
                        <phase>compile</phase>
                        <goals>
                            <goal>aggregate</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <doclet>com.glsx.platform.protocol.common.ExtractCommentsDoclet</doclet>
                    <docletPath>${project.build.directory}/classes</docletPath>
                    <reportOutputDirectory>${project.build.outputDirectory}/META-INF</reportOutputDirectory>
                    <useStandardDocletOptions>false</useStandardDocletOptions>
                </configuration>
            </plugin>
        </plugins>
    </build>

 */

public class ExtractCommentsDoclet {

    public static boolean start(RootDoc root) throws IOException {

        // 生成接口文档
        generateApiDoc(root, "接口文档.docx", "com.glsx.platform.protocol.common.service");

        // 生成数据库映射
        generateHibernateMapper(root, "", "com.glsx.platform.protocol.common.entity");

        // 生成DAO层
        generateDaoImpl(root, "com.glsx.platform.protocol.common.entity");

        // 生成接口实现层
        generateServiceImpl(root, "com.glsx.platform.protocol.common.service");

        return true;
    }


    /**
     * @brief 简单说明
     *
     * 在文档插入接口的简单说明
     *
     * @param doc
     * @param m
     */
    public static void insertMethodBrief(XWPFDocument doc, MethodDoc m) {
        XWPFParagraph p = doc.createParagraph();
        p.setStyle("2");
        StringBuilder b = new StringBuilder();
        for(int i = 0; i < m.paramTags().length; ++i) {
            if("brief".equals(m.paramTags()[i].parameterName())) {
                b.append(m.paramTags()[i].parameterComment());
                break;
            }
        }
        if(b.length() > 0) {
            b.append("(" + m.name() + ")");
        } else {
            b.append(m.name());
        }
        p.createRun().setText(b.toString());
    }

    /**
     * @brief 详细描述
     *
     * 插入接口详细描述
     * @param doc 文档对象
     * @param m 方法对象
     */
    public static void insertMethodDetail(XWPFDocument doc, MethodDoc m) {
        XWPFParagraph p = doc.createParagraph();
        XWPFRun r = p.createRun();
        r.setBold(true);
        r.setText("接口描述");

        p = doc.createParagraph();
        p.setIndentationFirstLine(400);
        p.createRun().setText(m.commentText());
    }


    /**
     * @brief 插入参数表格
     *
     * 插入表格，用于描述方法中的所有参数名称
     * @param document 文档对象
     * @param m 方法对象
     */
    public static void insertMetodParamterTable(XWPFDocument document, MethodDoc m) {
        Parameter[] parameters = m.parameters();
        int paramLength = parameters.length;
        if(paramLength == 0) {
            return;
        }

        XWPFParagraph p = document.createParagraph();
        XWPFRun r = p.createRun();
        r.setBold(true);
        r.setText("输入参数");

        int totalRow = paramLength+1;
        int totalCol = 4;

        String[][] content = new String[totalRow][totalCol];
        content[0][0] = "参数名称";
        content[0][1] = "类型";
        content[0][2] = "是否必填";
        content[0][3] = "描述信息";

        for (int i = 0; i < totalRow-1; i++) {
            Parameter param = parameters[i];

            content[i+1][0] = param.name();
            content[i+1][1] = param.typeName();
            content[i+1][2] = "Y";
            content[i+1][3] = "";

            for (ParamTag paramTag : m.paramTags()) {
                if (param.name().equals(paramTag.parameterName())) {
                    content[i+1][3] = paramTag.parameterComment();
                    break;
                }
            }
        }

        XWPFTable table = document.createTable(totalRow, totalCol);

        for(int row = 0; row < totalRow; row++) {
            XWPFTableRow tableRow = table.getRow(row);
            CTTrPr trPr = tableRow.getCtRow().addNewTrPr();
            CTHeight ht = trPr.addNewTrHeight();
            ht.setVal(BigInteger.valueOf(360L));

            for(int col = 0; col < totalCol; ++col) {
                XWPFTableCell cell = tableRow.getCell(col);

                CTTcPr tcpr = cell.getCTTc().addNewTcPr();
                CTVerticalJc va = tcpr.addNewVAlign();
                va.setVal(STVerticalJc.CENTER);

                CTShd ctshd = tcpr.addNewShd();
                ctshd.setColor("auto");
                ctshd.setVal(STShd.CLEAR);

                if(row == 0) {
                    ctshd.setFill("A7BFDE");
                }

                // 水平居中
                cell.getParagraphs().get(0).setAlignment(ParagraphAlignment.CENTER);
                cell.setText(content[row][col]);
            }
        }
    }


    public static void insertReturn(XWPFDocument doc, MethodDoc m) {
        XWPFParagraph p = doc.createParagraph();
        XWPFRun r = p.createRun();
        r.setBold(true);
        r.setText("返回参数");

        p = doc.createParagraph();
        p.setIndentationFirstLine(400);

        for(Tag tag : m.tags("return")) {
            p.createRun().setText(tag.text());
        }

        boolean found = false;
        for(int i = 0; i < m.paramTags().length; ++i) {
            p.createRun().setText("xxx " + m.paramTags()[i].name());
            p.createRun().addCarriageReturn();
            if("return".equals(m.paramTags()[i].name())) {
                found = true;
                ParamTag paramTag = m.paramTags()[i];
                p.createRun().setText(paramTag.parameterComment());
                break;
            }
        }
        if(!found) {
            p.createRun().setText("无");
        }

    }

    public static XWPFDocument createWithTemplateDoc(String templateFile) throws IOException {

        XWPFDocument template = null;
        if(templateFile != null && !"".equals(templateFile)) {
            File file = new File(templateFile);
            if(file.exists()) {
                template = new XWPFDocument(new FileInputStream(templateFile));
            }
        }

        XWPFDocument document= new XWPFDocument();

        if(template != null) {
            XWPFStyles newStyles = document.createStyles();
            try {
                for (CTStyle style : template.getStyle().getStyleArray()) {
                    System.out.print(style.getStyleId());
                }
                newStyles.setStyles(template.getStyle());
            } catch (XmlException e) {
                e.printStackTrace();
            }
        }
        return document;
    }


    /**
     * 生成API接口文档
     * @param root 文档根目录对象
     * @param filename 生成的文档路径及名称
     * @param packagePath 包路径
     * @throws IOException
     */
    public static void generateApiDoc(RootDoc root, String filename, String packagePath) throws IOException {


        XWPFDocument document = createWithTemplateDoc("myapi.docx");


        PackageDoc packageDoc = root.packageNamed(packagePath);

        for (ClassDoc c : packageDoc.allClasses()) {

            for (MethodDoc m : c.methods(false)) {

                insertMethodBrief(document, m);
                insertMethodDetail(document, m);
                insertMetodParamterTable(document, m);
                insertReturn(document, m);
            }
        }

        FileOutputStream out = new FileOutputStream(new File(filename));
        document.write(out);
        out.close();
    }

    public static void generateDatabaseTable(RootDoc root, String filename, String packagePath)  throws IOException{
        XWPFDocument document = createWithTemplateDoc("myapi.docx");

        PackageDoc packageDoc = root.packageNamed(packagePath);

        for (ClassDoc c : packageDoc.allClasses()) {
            for (FieldDoc f : c.fields(false)) {
                document.createParagraph().createRun().setText(f.name() + " " + f.commentText());
            }
        }

        FileOutputStream out = new FileOutputStream(new File(filename));
        document.write(out);
        out.close();
    }


    public static String field2name(String field) {
        StringBuilder builder = new StringBuilder(field.length() + 8);
        for(int i =0;i <field.length(); ++i) {
            if(Character.isLowerCase(field.charAt(i))) {
                builder.append(field.charAt(i));
            } else {
                if(i == 0) {
                    builder.append(Character.toLowerCase(field.charAt(i)));
                } else {
                    builder.append("_").append(Character.toLowerCase(field.charAt(i)));
                }
            }
        }
        return builder.toString();
    }


    public static void generateHibernateMapper(RootDoc root, String filename, String packagePath) throws IOException {

        PackageDoc packageDoc = root.packageNamed(packagePath);

        createPackagePath("entity");

        for (ClassDoc c : packageDoc.allClasses()) {



            org.w3c.dom.Document dom;
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

            try {
                DocumentBuilder db = dbf.newDocumentBuilder();
                dom = db.newDocument();

                Element rootEle = dom.createElement("hibernate-mapping");

                Element aClass = dom.createElement("class");
                aClass.setAttribute("name", c.qualifiedName());
                aClass.setAttribute("table", "platform_"+field2name(c.name()));

                int i = 0;
                for (ClassDoc o = c; i != 2 && o != null; o = o.superclass(), ++i) {
                    for (FieldDoc f : o.fields(false)) {

                        if(f.isStatic()) {
                            continue;
                        }

                        Element property = dom.createElement("property");
                        property.setAttribute("name", f.name());
                        property.setAttribute("type", f.type().qualifiedTypeName());

                        Element col = dom.createElement("column");
                        col.setAttribute("name", field2name(f.name()));
                        property.appendChild(col);

                        Element comment = dom.createElement("comment");
                        comment.setTextContent(f.commentText());
                        col.appendChild(comment);

                        aClass.appendChild(property);
                    }
                }

                rootEle.appendChild(aClass);
                dom.appendChild(rootEle);

                try {
                    Transformer tr = TransformerFactory.newInstance().newTransformer();
                    tr.setOutputProperty(OutputKeys.METHOD, "xml");
                    tr.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
                    tr.setOutputProperty(OutputKeys.VERSION, "1.0");
                    tr.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd");
                    tr.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "-//Hibernate/Hibernate Mapping DTD 3.0//EN");

                    String xmlName = "entity/" + c.name() + ".hbm.xml";

                    tr.transform(new DOMSource(dom),
                            new StreamResult(new OutputStreamWriter(new FileOutputStream(xmlName), "UTF-8")));

                } catch (TransformerException te) {
                    System.out.println(te.getMessage());
                } catch (IOException ioe) {
                    System.out.println(ioe.getMessage());
                }
            } catch (ParserConfigurationException pce) {
                System.out.println("UsersXML: Error trying to instantiate DocumentBuilder " + pce);
            }
        }
    }


    public static void createPackagePath(String packagePath) {
        String[] paths = packagePath.split("/");
        StringBuilder dir = new StringBuilder(packagePath.length()+1);
        for (int i = 0; i < paths.length; ++i) {
            if(i == 0) {
                dir.append(paths[i]);
            } else {
                dir.append("/").append(paths[i]);
            }

            File file = new File(dir.toString());

            if(!file.exists()) {
                file.mkdir();
            }
        }
    }


    public static void generateDaoImpl(RootDoc root, String packagePath) throws IOException {
        PackageDoc packageDoc = root.packageNamed(packagePath);

        createPackagePath("dao/impl");

        for (ClassDoc c : packageDoc.allClasses()) {
            FileOutputStream outputStream = new FileOutputStream("dao/" + c.name() + "Dao.java");
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
            bufferedWriter.write("public interface ");
            bufferedWriter.write(c.name()+"Dao ");
            bufferedWriter.write("extends BaseDao<");
            bufferedWriter.write(c.name() + ", ");
            bufferedWriter.write("Integer> {");
            bufferedWriter.newLine();
            bufferedWriter.write("}");
            bufferedWriter.close();
            outputStream.close();

            FileOutputStream outputStreamImpl = new FileOutputStream("dao/impl/" + c.name() + "DaoImpl.java");
            BufferedWriter bufferedWriterImpl = new BufferedWriter(new OutputStreamWriter(outputStreamImpl));
            bufferedWriterImpl.write("@Repository");
            bufferedWriterImpl.newLine();
            bufferedWriterImpl.write("public class ");
            bufferedWriterImpl.write(c.name()+"DaoImpl ");
            bufferedWriterImpl.write("extends BaseDaoImpl<");
            bufferedWriterImpl.write(c.name() + ", ");
            bufferedWriterImpl.write("Integer> implements ProtocolDao {");
            bufferedWriterImpl.newLine();
            bufferedWriterImpl.write("}");
            bufferedWriterImpl.close();
            outputStreamImpl.close();
        }

    }

    public static void generateServiceImpl(RootDoc root,  String packagePath) throws IOException {
        PackageDoc packageDoc = root.packageNamed(packagePath);
        createPackagePath("service/impl");

        for (ClassDoc c : packageDoc.allClasses()) {

            String className = c.name() + "Impl";
            String daoName = c.name().substring(0, c.name().indexOf("Service")) + "Dao";
            String daoVal = Character.toLowerCase(daoName.charAt(0)) + daoName.substring(1);

            FileOutputStream outputStream = new FileOutputStream("service/impl/" + className + ".java");
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
            bufferedWriter.write("public class ");
            bufferedWriter.write(className + " implements " + c.name() + " {");
            bufferedWriter.newLine();
            bufferedWriter.newLine();
            bufferedWriter.write("static private Logger logger = LoggerFactory.getLogger("+className+".class);");
            bufferedWriter.newLine();
            bufferedWriter.newLine();
            bufferedWriter.write("@Autowired");
            bufferedWriter.newLine();
            bufferedWriter.write(daoName + " " + daoVal + ";");
            bufferedWriter.newLine();

            for (MethodDoc m : c.methods()) {
                bufferedWriter.newLine();
                bufferedWriter.write("@Override");
                bufferedWriter.newLine();
                bufferedWriter.write("public ");
                bufferedWriter.write(m.returnType().typeName() + " ");
                bufferedWriter.write(m.name() + "(");
                Parameter[] parameters = m.parameters();
                for (int i = 0; i < parameters.length; ++i) {
                    Parameter p = parameters[i];
                    bufferedWriter.write(p.typeName() + " " + p.name());
                    if(parameters.length > 0 && i != parameters.length - 1) {
                        bufferedWriter.write(", ");
                    }
                }
                bufferedWriter.write(")" + " throws ServiceException {");
                bufferedWriter.newLine();
                bufferedWriter.write("}");
            }
            bufferedWriter.newLine();
            bufferedWriter.write("}");

            bufferedWriter.close();
            outputStream.close();
        }
    }


}